﻿/* SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
 */

using System;
using System.Net;
using System.Runtime.InteropServices;

namespace WireGuardNT_PInvoke.WireGuard
{
    public static class Win32
    {
        [Flags]
        public enum ScmAccessRights
        {
            Connect = 0x0001,
            CreateService = 0x0002,
            EnumerateService = 0x0004,
            Lock = 0x0008,
            QueryLockStatus = 0x0010,
            ModifyBootConfig = 0x0020,
            StandardRightsRequired = 0xF0000,
            AllAccess = (StandardRightsRequired | Connect | CreateService | EnumerateService | Lock | QueryLockStatus | ModifyBootConfig)
        }

        [Flags]
        public enum ServiceAccessRights
        {
            QueryConfig = 0x1,
            ChangeConfig = 0x2,
            QueryStatus = 0x4,
            EnumerateDependants = 0x8,
            Start = 0x10,
            Stop = 0x20,
            PauseContinue = 0x40,
            Interrogate = 0x80,
            UserDefinedControl = 0x100,
            Delete = 0x00010000,
            StandardRightsRequired = 0xF0000,
            AllAccess = (StandardRightsRequired | QueryConfig | ChangeConfig | QueryStatus | EnumerateDependants | Start | Stop | PauseContinue | Interrogate | UserDefinedControl)
        }

        [Flags]
        public enum ServiceStartType
        {
            Boot = 0x00000000,
            System = 0x00000001,
            Auto = 0x00000002,
            Demand = 0x00000003,
            Disabled = 0x00000004
        }

        [Flags]
        public enum ServiceControl
        {
            Stop = 0x00000001,
            Pause = 0x00000002,
            Continue = 0x00000003,
            Interrogate = 0x00000004,
            Shutdown = 0x00000005,
            ParamChange = 0x00000006,
            NetBindAdd = 0x00000007,
            NetBindRemove = 0x00000008,
            NetBindEnable = 0x00000009,
            NetBindDisable = 0x0000000A
        }

        [Flags]
        public enum ServiceError
        {
            Ignore = 0x00000000,
            Normal = 0x00000001,
            Severe = 0x00000002,
            Critical = 0x00000003
        }

        [Flags]
        public enum ServiceSidType
        {
            None = 0x00000000,
            Unrestricted = 0x00000001,
            Restricted = 0x00000003
        }

        [Flags]
        public enum ServiceType
        {
            KernelDriver = 0x00000001,
            FileSystemDriver = 0x00000002,
            Win32OwnProcess = 0x00000010,
            Win32ShareProcess = 0x00000020,
            InteractiveProcess = 0x00000100
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Size = 8192), ComVisible(false)]
        public struct ServiceSidInfo
        {
            public ServiceSidType serviceSidType;
        };

        public enum ServiceState
        {
            Unknown = -1,
            NotFound = 0,
            Stopped = 1,
            StartPending = 2,
            StopPending = 3,
            Running = 4,
            ContinuePending = 5,
            PausePending = 6,
            Paused = 7
        }

        [StructLayout(LayoutKind.Sequential)]
        public class ServiceStatus
        {
            public int dwServiceType = 0;
            public ServiceState dwCurrentState = 0;
            public int dwControlsAccepted = 0;
            public int dwWin32ExitCode = 0;
            public int dwServiceSpecificExitCode = 0;
            public int dwCheckPoint = 0;
            public int dwWaitHint = 0;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Size = 8192), ComVisible(false)]
        public struct ServiceDescription
        {
            public String lpDescription;
        };

        public enum ServiceConfigType
        {
            Description = 1,
            SidInfo = 5
        }

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct IN_ADDR
        {
            private uint _Addr;
            public IPAddress Addr
            {
                get
                {
                    return new IPAddress(unchecked((long)this._Addr));
                }

                set
                {

                    this._Addr = (uint)BitConverter.ToInt32(value.GetAddressBytes(), 0);
                }
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct IN6_ADDR
        {
            private uint _addrA;
            private uint _addrB;
            private uint _addrC;
            private uint _addrD;
            public IPAddress Addr
            {
                get
                {
                    var b1 = BitConverter.GetBytes(_addrA);
                    var b2 = BitConverter.GetBytes(_addrB);
                    var b3 = BitConverter.GetBytes(_addrC);
                    var b4 = BitConverter.GetBytes(_addrD);
                    var bytes = new byte[] {
                        b1[0], b1[1], b1[2], b1[3],
                        b2[0], b2[1], b2[2], b2[3],
                        b3[0], b3[1], b3[2], b3[3],
                        b4[0], b4[1], b4[2], b4[3]
                    };

                    return new IPAddress(bytes);
                }

                set
                {
                    var valueBytes = value.GetAddressBytes();
                    
                    if (valueBytes.Length != 16)
                    {
                        throw new ArgumentException("Not a valid IPV6 address.", nameof(Addr));
                    }

                    _addrA = BitConverter.ToUInt32(valueBytes, 0);
                    _addrB = BitConverter.ToUInt32(valueBytes, 4);
                    _addrC = BitConverter.ToUInt32(valueBytes, 8);
                    _addrD = BitConverter.ToUInt32(valueBytes, 12);
                }
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SOCKADDR_IN
        {
            public ADDRESS_FAMILY sin_family;
            public ushort sin_port;
            public IN_ADDR sin_addr;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SOCKADDR_IN6
        {
            public ADDRESS_FAMILY sin6_family;
            public ushort sin6_port;
            public uint sin6_flowinfo;
            public IN6_ADDR sin6_addr;
            public uint sin6_scope_id;
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct SOCKADDR_INET
        {
            [FieldOffset(0)]
            [MarshalAs(UnmanagedType.Struct)]
            public SOCKADDR_IN Ipv4;

            [FieldOffset(0)]
            [MarshalAs(UnmanagedType.Struct)]
            public SOCKADDR_IN6 Ipv6;

            [FieldOffset(0)]
            public ADDRESS_FAMILY si_family;
            
        }

        public enum ADDRESS_FAMILY : UInt16
        {
            AF_UNSPEC = 0,
            AF_INET = 2,
            AF_INET6 = 23
        }
    }
}
